﻿using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using ManagedUIFlow.Infrastructure.Commands;
using ManagedUIFlow.Model;
using Moq;
using Moq.Protected;
using Xunit;

namespace ManagedUIFlow.Specs.Infrastructure.Commands
{
    public class ControllerActionSpecs
    {
        [Fact]
        public void ExecuteActionWithNullFlowAndContext_Throws()
        {
            Assert.Throws<ArgumentNullException>(
                () => new FlowAction(null).Execute(null));
        }

        [Fact]
        public void ExecuteActionWithRouteWithoutController_Throws()
        {
            var contextMock = new Mock<HttpContextBase>();
            var responseMock = new Mock<HttpResponseBase>();
            contextMock.Setup(c => c.Response).Returns(responseMock.Object);
            var rd = new RouteData();
            var flow = new ManagedFlow(null);
            var controllAction = new FlowAction(new RequestContext(contextMock.Object, rd));

            Assert.Throws<InvalidOperationException>(
                "The controller name should be a non-empty string value.",
                () => controllAction.Execute(flow));
        }

        [Fact]
        public void InstantiateControllerActionToExecute_CallsExecute()
        {            
            var contextMock = new Mock<HttpContextBase>();
            var responseMock = new Mock<HttpResponseBase>();
            contextMock.Setup(c => c.Response).Returns(responseMock.Object);
            var rd = new RouteData();
            rd.Values.Add("controller", "foo");
            var requestContext = new RequestContext(contextMock.Object, rd);
            var flow = new ManagedFlow(rd.Values["controller"].ToString());
            var controllerAction = new FlowAction(requestContext);
            var controllerMock = new Mock<ControllerBase>();
            controllerMock.Protected().Setup("Execute", requestContext).Verifiable();
            var cb = new ControllerBuilder();
            var controllerFactoryMock = new Mock<IControllerFactory>();
            controllerFactoryMock.Setup(o => o.CreateController(requestContext, "foo")).Returns(controllerMock.Object);
            controllerFactoryMock.Setup(o => o.ReleaseController(controllerMock.Object));
            cb.SetControllerFactory(controllerFactoryMock.Object);
            controllerAction.ControllerBuilder = cb;

            controllerAction.Execute(flow);

            controllerMock.Verify();
        }

        [Fact]
        public void IfExecuteActionDoesNotThrowException_DisposesController()
        {
            var mockController = new Mock<ControllerBase>();
            mockController.Protected().Setup("Execute", ItExpr.IsAny<RequestContext>()).Verifiable();
            mockController.As<IDisposable>().Setup(d => d.Dispose()).AtMostOnce().Verifiable();
            var builder = new ControllerBuilder();
            builder.SetControllerFactory(new SimpleControllerFactory(mockController.Object));
            var contextMock = new Mock<HttpContextBase>();
            var responseMock = new Mock<HttpResponseBase>();
            contextMock.Setup(c => c.Response).Returns(responseMock.Object);
            var requestContext = new RequestContext(contextMock.Object, new RouteData());
            requestContext.RouteData.Values["controller"] = "fooController";
            var flow = new ManagedFlow(requestContext.RouteData.Values["controller"].ToString());

            new FlowAction(requestContext)
                {
                    ControllerBuilder = builder
                }.Execute(flow);

            mockController.Verify();
        }

        [Fact]
        public void ProcessRequestDisposesControllerIfExecuteThrowsException()
        {
            var mockController = new Mock<ControllerBase>(MockBehavior.Strict);
            mockController.Protected().Setup("Execute", ItExpr.IsAny<RequestContext>()).Throws(new Exception("some exception"));
            mockController.As<IDisposable>().Setup(d => d.Dispose()).AtMostOnce().Verifiable();

            var builder = new ControllerBuilder();
            builder.SetControllerFactory(new SimpleControllerFactory(mockController.Object));

            var contextMock = new Mock<HttpContextBase>();
            var responseMock = new Mock<HttpResponseBase>();
            contextMock.Setup(c => c.Response).Returns(responseMock.Object);
            var requestContext = new RequestContext(contextMock.Object, new RouteData());
            requestContext.RouteData.Values["controller"] = "fooController";
            var flow = new ManagedFlow(requestContext.RouteData.Values["controller"].ToString());

            Assert.Throws<Exception>(() => new FlowAction(requestContext)
                                               {
                                                   ControllerBuilder = builder
                                               }.Execute(flow));

            // Assert
            mockController.Verify();
            responseMock.Verify();
        }

        private class SimpleControllerFactory : IControllerFactory
        {
            private IController _instance;

            public SimpleControllerFactory(IController instance)
            {
                _instance = instance;
            }

            public IController CreateController(RequestContext context, string controllerName)
            {
                return _instance;
            }

            public void ReleaseController(IController controller)
            {
                var disposable = controller as IDisposable;
                if (disposable != null)
                {
                    disposable.Dispose();
                }
            }
        }
    }
}